package sql.record;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import logging.Monitor;


/**
 * This class encapsulates a record from the database.
 * The table name we insert to, is the name of the inheriting class.
 * Additionally, the schema names and types are specified by the inheriting class. 
 * @author Ron Galay
 *
 */
public abstract class TableRecord implements Cloneable{

	
	/**
	 * Updates via reflection the fields of the object
	 * @param record a mapping of field names to their values
	 */
	public void update(Map<String,Object> record) {
		
		for(Field field: this.getClass().getDeclaredFields()) {

			String fieldName = field.getName();
			Object value = null;
			// updating the value of the field
			try {
				value = record.get(fieldName);
				field.set(this, value);
			} catch (Exception e) {
				// ignore - cannot happen
			}
		}
	}
	
	/**
	 * @return a mapping of the objects field names and their sql type
	 */
	public Map<String,String> getSchema() {
		
		Map<String,String> schemaDefenition = new HashMap<String,String>();
		
		for(Field field: this.getClass().getFields()) {

			String fieldName = field.getName();
			String type = field.getAnnotation(SQLType.class).type();
			schemaDefenition.put(fieldName, type);
		}
		
		return schemaDefenition;
	}
	
	/**
	 * @return a mapping of the objects field names and their values
	 */
	public Map<String,Object> getValues() {
		
		Map<String,Object> recordValues = new HashMap<String,Object>();
		
		for(Field field: this.getClass().getFields()) {

			String fieldName = field.getName();
			Class<?> type = field.getType();
			try {
				// getting the field value
				Object value = type.cast(field.get(this));
				Monitor.write(this,	fieldName+" with value:"+value);
				recordValues.put(fieldName, value);
				
			} catch (Exception e) {
				// ignore - cannot happen
			}
		}
		
		return recordValues;
	}
	
	/**
	 * @return a mapping of the objects field names and their java type
	 */
	public Map<String,String> getType() {
		
		Map<String,String> recordTypes = new HashMap<String,String>();
		
		for(Field field: this.getClass().getFields()) {

			String fieldName = field.getName();
			Class<?> type = field.getType();
			recordTypes.put(fieldName,type.getSimpleName());
		}
		
		return recordTypes;
	}
	
	public Object[] getArrayValues() {
		
		List<Object> arrayType = new ArrayList<Object>();
		
		for(Field field: this.getClass().getFields()) {

			Object value = null;
			try {
				value = field.get(this);
			} catch (Exception e) {
				// ignore - cannot happen
			}
			arrayType.add(value);
		}
		
		return arrayType.toArray( new Object[]{});
		
	}
	
	public String[] getArrayType() {
		
		List<String> arrayType = new ArrayList<String>();
		
		for(Field field: this.getClass().getFields()) {

			Class<?> type = field.getType();
			arrayType.add(type.getSimpleName());
		}
		
		return arrayType.toArray( new String[]{});
	}
	
	/**
	 * @return the marked prime key of the table or null otherwise 
	 */
	public String getPrimeKeyName() {
	
		String primeCol = null;
		for(Field field: this.getClass().getFields()) {

			String fieldName = field.getName();
			if(field.getAnnotation(SQLType.class).primeKey()) {
				primeCol = fieldName;
				break;
			}
		}
		return primeCol;
	}
	
	/**
	 * @return the marked search field of the table or null otherwise
	 */
	public String getSearchName() {
		
		String searchName = null;
		for(Field field: this.getClass().getFields()) {

			String fieldName = field.getName();
			if(field.getAnnotation(SQLType.class).searchBy()) {
				searchName = fieldName;
				break;
			}
		}
		return searchName;
	}
	
	public List<String> getFields() {
		
		List<String> fields = new LinkedList<String>();
		for(Field field: this.getClass().getFields()) {
			
			String fieldName = field.getName();
			fields.add(fieldName);
		}
		
		return fields;
	}
	
	/**
	 * A getter to return the table name
	 */
	public String getTableName() {
		return this.getClass().getSimpleName();
	}
	
	/**
	 * This method returns an instance of the extending class
	 */
	protected abstract TableRecord getInstanceFactory();
	
	@Override
	public TableRecord clone() {
		
		TableRecord clone = getInstanceFactory();
		Class<?> cloneClass = clone.getClass();
		for (Field field : this.getClass().getFields()) {
			
			String name = field.getName();
			Object value;
			try {
				value = field.get(this);
				cloneClass.getField(name).set(clone, value);
			} catch (Exception e) {
				// ignore
			}
		}
		return clone;
	}
	
	@Override
	public String toString() {
		
		String objString = "";
		for (Field field : this.getClass().getFields()) {
			String name = field.getName();
			Object value;
			try {
				value = field.get(this);
				objString+=name+":"+value+" ";
			} catch (Exception e) {
				// ignore
			}
		}
		
		return objString;
	}
}
